-
Notifications
You must be signed in to change notification settings - Fork 66
Bxdf fixes cook torrance #930
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Schlick<T> getReorientedFresnel(const scalar_type NdotI) NBL_CONST_MEMBER_FUNC | ||
{ | ||
// correct? but also sclick works best between eta 1.4-2.2 | ||
OrientedEtaRcps<T> rcpEta = getOrientedEtaRcps(); | ||
T sqrt_newF0 = (hlsl::promote<T>(1.0) - rcpEta.value) / (hlsl::promote<T>(1.0) + rcpEta.value); | ||
return Schlick<T>::create(sqrt_newF0 * sqrt_newF0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Schlick can't bea twosided fresnel, it only works for the front face
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((fresnel.getOrientedEtaRcps()), ::nbl::hlsl::is_same_v, OrientedEtaRcps<typename T::vector_type>)) | ||
); | ||
#undef cosTheta | ||
#undef fresnel | ||
#include <nbl/builtin/hlsl/concepts/__end.hlsl> | ||
|
||
#define NBL_CONCEPT_NAME TwoSidedFresnel | ||
#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) | ||
#define NBL_CONCEPT_TPLT_PRM_NAMES (T) | ||
#define NBL_CONCEPT_PARAM_0 (fresnel, T) | ||
#define NBL_CONCEPT_PARAM_1 (cosTheta, typename T::scalar_type) | ||
NBL_CONCEPT_BEGIN(2) | ||
#define fresnel NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 | ||
#define cosTheta NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 | ||
NBL_CONCEPT_END( | ||
((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Fresnel, T)) | ||
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((fresnel.getRefractionOrientedEta()), ::nbl::hlsl::is_same_v, OrientedEtas<typename T::vector_type>)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whats the difference between getOrientedEtaRcps
and getRefractionOrientedEta
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, one is the reciprocal, btw getRefractionOrientedEta
must be a scalar type return cause its used for geometric refraction
namespace dummy_impl | ||
{ | ||
using sample_t = SLightSample<ray_dir_info::SBasic<float> >; | ||
using interaction_t = surface_interactions::SAnisotropic<surface_interactions::SIsotropic<ray_dir_info::SBasic<float> > >; | ||
using cache_t = SAnisotropicMicrofacetCache<SIsotropicMicrofacetCache<float> >; | ||
} | ||
|
||
#define NBL_CONCEPT_NAME NDF | ||
#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) | ||
#define NBL_CONCEPT_TPLT_PRM_NAMES (T) | ||
#define NBL_CONCEPT_PARAM_0 (ndf, T) | ||
#define NBL_CONCEPT_PARAM_1 (query, dummy_impl::MetaQuery) | ||
#define NBL_CONCEPT_PARAM_1 (quant_query, typename T::quant_query_type) | ||
#define NBL_CONCEPT_PARAM_2 (_sample, dummy_impl::sample_t) | ||
#define NBL_CONCEPT_PARAM_3 (interaction, dummy_impl::interaction_t) | ||
#define NBL_CONCEPT_PARAM_4 (cache, dummy_impl::cache_t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't you take the float
in the dummy_impl to be T::scalar_type
or something from the NDF
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why but I'm unable to do
template<typename T>
using sample_t = SLightSample<ray_dir_info::SBasic<T> >;
and pass in T::scalar_type
without the concept not working
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't you do typename T::scalar_type
or something ?
NBL_CONSTEXPR_STATIC_INLINE bool IsAnisotropic = _IsAnisotropic; | ||
NBL_CONSTEXPR_STATIC_INLINE MicrofacetTransformTypes NDFSurfaceType = reflect_refract; | ||
NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = reflect_refract != MTT_REFLECT; | ||
|
||
using this_t = GGX<T, _IsAnisotropic, reflect_refract>; | ||
using scalar_type = T; | ||
using base_type = impl::GGXCommon<T,IsBSDF,IsAnisotropic>; | ||
using quant_type = SDualMeasureQuant<scalar_type>; | ||
using vector2_type = vector<T, 2>; | ||
using vector3_type = vector<T, 3>; | ||
|
||
using dg1_query_type = impl::SGGXDG1Query<scalar_type>; | ||
using g2g1_query_type = impl::SGGXG2XQuery<scalar_type>; | ||
using quant_query_type = impl::NDFQuantQuery<scalar_type>; | ||
|
||
NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = IsBSDF ? BxDFClampMode::BCM_ABS : BxDFClampMode::BCM_NONE; | ||
template<class Interaction> | ||
NBL_CONSTEXPR_STATIC_INLINE bool RequiredInteraction = IsAnisotropic ? surface_interactions::Anisotropic<Interaction> : surface_interactions::Isotropic<Interaction>; | ||
template<class MicrofacetCache> | ||
NBL_CONSTEXPR_STATIC_INLINE bool RequiredMicrofacetCache = IsAnisotropic ? AnisotropicMicrofacetCache<MicrofacetCache> : ReadableIsotropicMicrofacetCache<MicrofacetCache>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks to me like every NDF needs to define:
this_t
scalar_type
vector2_type
vector3_type
IsAnisotropic
IsBSDF // this is badly named, should be SupportsTransmission or !ReflectanceOnly cause NDF is a concept apart and below a BSDF
NDFSurfaceType // should be renmaed to avoid the NDF::NDF tautology, maybe SupportedPaths
_Clamp
RequiredInteraction
RequiredMicrofacetCache
lets make it into a macro in ndf.hlsl
static scalar_type G2_over_G1(NBL_CONST_REF_ARG(Query) query, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
scalar_type onePlusLambda_V = scalar_type(1.0) + query.getLambdaV(); | ||
return onePlusLambda_V * hlsl::mix(scalar_type(1.0)/(onePlusLambda_V + query.getLambdaL()), bxdf::beta<scalar_type>(onePlusLambda_V, scalar_type(1.0) + query.getLambdaL()), cache.isTransmission()); | ||
scalar_type lambda_L = query.getLambdaL(); | ||
return onePlusLambda_V * hlsl::mix(scalar_type(1.0)/(onePlusLambda_V + lambda_L), bxdf::beta<scalar_type>(onePlusLambda_V, scalar_type(1.0) + lambda_L), cache.isTransmission()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method seems identical for Anisotropic and Isotropic bechmann, why specialize twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually 3 or 4 methods (G2_over_G1, correlated, Lambda, DG1 Query overload) here seem identical with templated queries, its just the template constraint on the cache that changes, should be moved down into the most derived class
template<class MicrofacetCache, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && !IsBSDF, quant_query_type> createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta) | ||
{ | ||
quant_query_type dummy; // brdfs don't make use of this | ||
return dummy; | ||
} | ||
template<class MicrofacetCache, typename C=bool_constant<IsBSDF> NBL_FUNC_REQUIRES(RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && IsBSDF, quant_query_type> createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta) | ||
{ | ||
quant_query_type quant_query; | ||
quant_query.VdotHLdotH = cache.getVdotHLdotH(); | ||
quant_query.VdotH_etaLdotH = cache.getVdotH() + orientedEta * cache.getLdotH(); | ||
return quant_query; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, NBL_FUNC_REQUIRES
is itself an enable_if, so I wouldn't write
template<class MicrofacetCache, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(RequiredMicrofacetCache<MicrofacetCache>)
enable_if_t<C::value && !IsBSDF, quant_query_type> createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
but simply
template<class MicrofacetCache, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(RequiredMicrofacetCache<MicrofacetCache> && C::value && !IsBSDF)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
Also in cases where the if-else branch wont generate invalid code, you can use NBL_IF_CONSTEXPR
without worry
template<class MicrofacetCache> NBL_FUNC_REQUIRES(RequiredMicrofacetCache<MicrofacetCache>)
quant_query_type createQuantQuery(NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta)
{
quant_query_type retval; // only has members for refraction
NBL_IF_CONSTEXPR(SupportsTransmission)
{
retval.VdotHLdotH = cache.getVdotHLdotH();
retval.VdotH_etaLdotH = cache.getVdotH() + orientedEta * cache.getLdotH();
}
return retval;
}
Finally, don't the getVdotH
and getLdotH
need ABS clamping or something?
If no, explain why in the comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is the NDFQuantQuery
which seems only useful for ndf/microfacet_to_light_transform.hlsl
not defined there? and outside of impl
with a better name?
template<class LS, class Interaction, class MicrofacetCache, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && !IsBSDF, quant_type> D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
scalar_type d = __ndf_base.template D<MicrofacetCache>(cache); | ||
return createDualMeasureQuantity<T>(d, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX)); | ||
} | ||
template<class LS, class Interaction, class MicrofacetCache, typename C=bool_constant<IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction> && RequiredMicrofacetCache<MicrofacetCache>) | ||
enable_if_t<C::value && IsBSDF, quant_type> D(NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
scalar_type d = __ndf_base.template D<MicrofacetCache>(cache); | ||
return createDualMeasureQuantity<T, reflect_refract>(d, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH()); | ||
} | ||
|
||
template<class LS, class Interaction, typename C=bool_constant<!IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction>) | ||
enable_if_t<C::value && !IsBSDF, quant_type> DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction) | ||
{ | ||
scalar_type dg1 = base_type::template DG1<dg1_query_type>(query); | ||
return createDualMeasureQuantity<T>(dg1, interaction.getNdotV(BxDFClampMode::BCM_MAX), _sample.getNdotL(BxDFClampMode::BCM_MAX)); | ||
} | ||
template<class LS, class Interaction, typename C=bool_constant<IsBSDF> NBL_FUNC_REQUIRES(LightSample<LS> && RequiredInteraction<Interaction>) | ||
enable_if_t<C::value && IsBSDF, quant_type> DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction) | ||
{ | ||
scalar_type dg1 = base_type::template DG1<dg1_query_type>(query); | ||
return createDualMeasureQuantity<T, reflect_refract>(dg1, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query.getVdotHLdotH(), quant_query.getVdotH_etaLdotH()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw you could have a createDualMeasureQuantity
that takes the Quant Query instead of individual VdotHLdotH
and VdotH_etaLDotH
arguments, then you wouldn't need two copies each of D and DG1
template<class Query NBL_FUNC_REQUIRES(ggx_concepts::DG1Query<Query>) | ||
static scalar_type DG1(NBL_CONST_REF_ARG(Query) query) | ||
{ | ||
return scalar_type(0.5) * query.getNdf() * query.getG1over2NdotV(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is identical for isotropic and anisotropic, and so are the query taking methods: G1_wo_numerator_devsh_part
, correlated_wo_numerator
, correlated
, G2_over_G1
as long as your requires
clauses use RequiredInteraction
and RequiredMicrofacetCache
template<class N, class F, bool IsBSDF> | ||
struct quant_query_helper; | ||
|
||
template<class N, class F> | ||
struct quant_query_helper<N, F, true> | ||
{ | ||
using quant_query_type = typename N::quant_query_type; | ||
|
||
template<class C> | ||
static quant_query_type __call(NBL_REF_ARG(N) ndf, NBL_CONST_REF_ARG(F) fresnel, NBL_CONST_REF_ARG(C) cache) | ||
{ | ||
return ndf.template createQuantQuery<C>(cache, fresnel.orientedEta.value[0]); | ||
} | ||
}; | ||
|
||
template<class N, class F> | ||
struct quant_query_helper<N, F, false> | ||
{ | ||
using quant_query_type = typename N::quant_query_type; | ||
|
||
template<class C> | ||
static quant_query_type __call(NBL_REF_ARG(N) ndf, NBL_CONST_REF_ARG(F) fresnel, NBL_CONST_REF_ARG(C) cache) | ||
{ | ||
typename N::scalar_type dummy; | ||
return ndf.template createQuantQuery<C>(cache, dummy); | ||
} | ||
}; | ||
|
||
template<class F, bool IsBSDF> | ||
struct check_TIR_helper; | ||
|
||
template<class F> | ||
struct check_TIR_helper<F, false> | ||
{ | ||
template<class MicrofacetCache> | ||
static bool __call(NBL_CONST_REF_ARG(F) fresnel, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
return true; | ||
} | ||
}; | ||
|
||
template<class F> | ||
struct check_TIR_helper<F, true> | ||
{ | ||
template<class MicrofacetCache> | ||
static bool __call(NBL_CONST_REF_ARG(F) fresnel, NBL_CONST_REF_ARG(MicrofacetCache) cache) | ||
{ | ||
return cache.isValid(fresnel.getRefractionOrientedEta()); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its possible to move these into NBL_IUF_CONSTEXPR
if you just made a struct which just concerns itself with
scalar_type orientedEta = impl::get_orientedEta_helper(fresnel); // return `fresnel.getRefractionOrientedEta()` if `SupportsTransmission` requiring a twosided fresnel, otherwise return uninitialized var
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well your getOrientedFrensel
already does what my suggested getOrientedEta_helper
should do, (minus a call to getRefractionOrientedEta()
NBL_IF_CONSTEXPR(IsBSDF) | ||
{ | ||
_f = impl::getOrientedFresnel<fresnel_type, IsBSDF>::__call(fresnel, interaction.getNdotV()); | ||
valid = impl::check_TIR_helper<fresnel_type, IsBSDF>::template __call<MicrofacetCache>(_f, cache); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you don't need check_TIR_helper
, just call the cache.isValid()
directly
scalar_type dummy; | ||
quant_query_type qq = ndf.template createQuantQuery<MicrofacetCache>(cache, dummy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whats the dummy in place of?
ray_dir_info_type invalidL; | ||
invalidL.makeInvalid(); | ||
return sample_type::createFromTangentSpace(invalidL, interaction.getFromTangentSpace()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's probably a faster way to make an invalid sample, require a createInvalid
factory
template<typename C=bool_constant<!IsBSDF> > | ||
enable_if_t<C::value && !IsBSDF, sample_type> generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u, NBL_REF_ARG(anisocache_type) cache) | ||
{ | ||
if (interaction.getNdotV() > numeric_limits<scalar_type>::min) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
either rewrite the if
so its >
and put the valid case code inside, or assert that getNdotV
is not NaN, as it stands a NaN will proceed
L.makeInvalid(); // should check if sample direction is invalid | ||
|
||
const vector3_type T = interaction.getT(); | ||
const vector3_type B = interaction.getB(); | ||
const vector3_type _N = interaction.getN(); | ||
|
||
return sample_type::create(L, T, B, _N); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should really be just a sample_type::createInvalid
and this vode with TBN should go into the valid if-case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the NdotL computation should be overwritten by 2.f*cache.getVdotH()*localH.z - localV.z
which you compute for the if-statement (the quantitiy you check to see if the sample has invalid path).
ray_dir_info_type L; | ||
if (scalar_type(2.0) * VdotH * localH.z > localV.z) // NdotL>0, compiler's Common Subexpression Elimination pass should re-use 2*VdotH later | ||
{ | ||
assert(VdotH >= scalar_type(0.0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can asset this as soon as VdotH
is known with a comment about VNDF sampling
// fail if samples have invalid paths | ||
const scalar_type NdotL = scalar_type(2.0) * VdotH * localH.z - localV.z; | ||
if ((ComputeMicrofacetNormal<scalar_type>::isTransmissionPath(NdotV, NdotL) != transmitted)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hoist this check all the way to when VdotH
and transmitted
is first calculated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be good to record all this in a comment #930 (comment)
const vector3_type upperHemisphereV = ieee754::flipSignIfRHSNegative<vector3_type>(localV, hlsl::promote<vector3_type>(NdotV)); | ||
const vector3_type localH = ndf.generateH(upperHemisphereV, u.xy); | ||
const scalar_type VdotH = hlsl::dot(localV, localH); | ||
const vector3_type H = hlsl::mul(interaction.getFromTangentSpace(), localH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
push calculation till last moment
const vector3_type B = interaction.getB(); | ||
|
||
// fail if samples have invalid paths | ||
const scalar_type NdotL = scalar_type(2.0) * VdotH * localH.z - localV.z; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not a valid computation of NdotL for the BSDF, this is the reflection equation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you follow
Nabla/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl
Lines 273 to 281 in 069f499
vector_type operator()(const bool doRefract, const scalar_type rcpOrientedEta, NBL_REF_ARG(scalar_type) out_IdotTorR) NBL_CONST_MEMBER_FUNC | |
{ | |
scalar_type NdotI = getNdotR(); | |
const scalar_type a = hlsl::mix<scalar_type>(1.0f, rcpOrientedEta, doRefract); | |
const scalar_type b = NdotI * a + getNdotTorR(doRefract, rcpOrientedEta); | |
// assuming `I` is normalized | |
out_IdotTorR = NdotI * b - a; | |
return refract.N * b - refract.I * a; | |
} |
then using the NdotH==localH.z
, NdotV
and the LdotH
you already compute for cache.isValid()
you can compute NdotL
as:
const float viewShortenFactor = hlsl::mix<scalar_type>(1.0f, rcpOrientedEta, transmitted);
const float NdotL = localH.z * (VdotH*viewShortenFactor + LdotH) - NdotV * viewShortenFactor;
const vector3_type _N = interaction.getN(); | ||
Refract<scalar_type> r = Refract<scalar_type>::create(V.getDirection(), H); | ||
const scalar_type LdotH = hlsl::mix(VdotH, r.getNdotT(rcpEta.value2[0]), transmitted); | ||
cache = anisocache_type::createPartial(VdotH, LdotH, hlsl::dot(_N, H), transmitted, rcpEta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you don't need to compute dot(_N,H)
its literally localH.z
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also assert that the cache.isValid()
right away after this (it should be, by construction)
return rr(NdotTorR, rcpOrientedEta); | ||
} | ||
bxdf::ReflectRefract<scalar_type> rr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leave a comment that the call rr
makes to getNdotTorR
and mix
as well as a good part of the comuptations should CSE with our computation of NdotL
(which should be hoisted above the reflect/refract code)
template<typename C=bool_constant<!IsAnisotropic> > | ||
enable_if_t<C::value && !IsAnisotropic, scalar_type> pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) cache) | ||
{ | ||
if (IsBSDF || (_sample.getNdotL() > numeric_limits<scalar_type>::min && interaction.getNdotV() > numeric_limits<scalar_type>::min)) | ||
{ | ||
scalar_type _pdf = __pdf<isotropic_interaction_type, isocache_type>(_sample, interaction, cache); | ||
return hlsl::mix(scalar_type(0.0), _pdf, _pdf < bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity)); | ||
} | ||
else | ||
return scalar_type(0.0); | ||
} | ||
scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) cache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can collapse the two methods if you use a REQUIRES(RequiredInteraction<Interaction> && RequiredCache<Cache>)
similarly for other methods
template<typename C=bool_constant<!IsAnisotropic> > | ||
enable_if_t<C::value && !IsAnisotropic, quotient_pdf_type> quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) cache) | ||
{ | ||
return __quotient_and_pdf<isotropic_interaction_type, isocache_type>(_sample, interaction, cache); | ||
} | ||
quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) cache) | ||
{ | ||
return __quotient_and_pdf<anisotropic_interaction_type, anisocache_type>(_sample, interaction, cache); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could get rid of simple passthrough if you put a REQUIRES
on the __quotient_and_pdf
and removed the __
quant_type D = ndf.template D<sample_type, Interaction, MicrofacetCache>(qq, _sample, interaction, cache); | ||
scalar_type DG = D.projectedLightMeasure; | ||
if (D.microfacetMeasure < bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity)) | ||
{ | ||
using g2g1_query_type = typename ndf_type::g2g1_query_type; | ||
g2g1_query_type gq = ndf.template createG2G1Query<sample_type, Interaction>(_sample, interaction); | ||
DG *= ndf.template correlated<sample_type, Interaction>(gq, _sample, interaction); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm actually since Eval is not Quotient, it should return 0 whenever PDF would have been INF, and therefore whenever Eval is inf, you can just return 0
you could even add an NBL_REF_ARG(bool)
to the D
signature for a boolean side-channel of isInf
because it pays for the NDF to set that boolean as soon as possible (before a chain or multiplies and divides, compiler won't hoiust your INF checks before divisions by measure factors, etc.)
quant_type D = ndf.template D<sample_type, Interaction, MicrofacetCache>(qq, _sample, interaction, cache); | ||
scalar_type DG = D.projectedLightMeasure; | ||
if (D.microfacetMeasure < bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity)) | ||
{ | ||
using g2g1_query_type = typename ndf_type::g2g1_query_type; | ||
g2g1_query_type gq = ndf.template createG2G1Query<sample_type, Interaction>(_sample, interaction); | ||
DG *= ndf.template correlated<sample_type, Interaction>(gq, _sample, interaction); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we must let NDF provide a Dcorrelated
method, and if detected, use that instead of this, because we miss an optimization opportunity with GGX
We could actually do this in the following way
using quant_query_type = typename ndf_type::quant_query_type;
scalar_type dummy;
quant_query_type qq = ndf.template createQuantQuery<MicrofacetCache>(cache, dummy);
quant_type D = ndf.template D<sample_type, Interaction, MicrofacetCache>(qq, _sample, interaction, cache);
scalar_type DG = D.projectedLightMeasure;
if (D.microfacetMeasure < bit_cast<scalar_type>(numeric_limits<scalar_type>::infinity))
{
using g2g1_query_type = typename ndf_type::g2g1_query_type;
g2g1_query_type gq = ndf.template createG2G1Query<sample_type, Interaction>(_sample, interaction);
DG *= ndf.template correlated<sample_type, Interaction>(gq, _sample, interaction);
}
// overwrites DG with NDF's `Dcorrelated` method, also `asserts` that the optimal method returns similar to above
impl::overwrite_DG<ndf_type,sample_type,Interaction,Cache>(/*NBL_REF_ARG*/DG,ndf,...);
continues #930 (comment)
using scalar_type = T; | ||
|
||
scalar_type getNdf() NBL_CONST_MEMBER_FUNC { return ndf; } | ||
scalar_type getG1over2NdotV() NBL_CONST_MEMBER_FUNC { return G1_over_2NdotV; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
want to either comment or stick it in the name or the Query concept comment that its the wo_numerator
static scalar_type DG1(NBL_CONST_REF_ARG(Query) query) | ||
{ | ||
return scalar_type(0.5) * query.getNdf() * query.getG1over2NdotV(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Query is not G1, this kinda seems to not make sense to me as a thing to return from DG1, I'm expecting NDF measure times the real G1 from a function called DG1
A rename might be in order, into DG1over4NdotV
?
…back into OrientedEta struct
Description
Continues #899 , #916 and #919
Testing
TODO list: